﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Xml.Linq;


namespace Novacode
{
    /// <summary>
    /// Represents a document paragraph.
    /// </summary>
    public partial class Paragraph : InsertBeforeOrAfter
    {
        /// <summary>
        /// Replaces first occurrences of a specified System.String in this instance, with another specified System.String.
        /// (nvtrung@gmail.com)
        /// </summary>
        /// <example>
        /// <code>
        /// // Load a document using a relative filename.
        /// using (DocX document = DocX.Load(@"C:\Example\Test.docx"))
        /// {
        ///     // The formatting to match.
        ///     Formatting matchFormatting = new Formatting();
        ///     matchFormatting.Size = 10;
        ///     matchFormatting.Italic = true;
        ///     matchFormatting.FontFamily = new FontFamily("Times New Roman");
        ///
        ///     // The formatting to apply to the inserted text.
        ///     Formatting newFormatting = new Formatting();
        ///     newFormatting.Size = 22;
        ///     newFormatting.UnderlineStyle = UnderlineStyle.dotted;
        ///     newFormatting.Bold = true;
        ///
        ///     // Iterate through the paragraphs in this document.
        ///     foreach (Paragraph p in document.Paragraphs)
        ///     {
        ///         /* 
        ///          * Replace all instances of the string "wrong" with the string "right" and ignore case.
        ///          * Each inserted instance of "wrong" should use the Formatting newFormatting.
        ///          * Only replace an instance of "wrong" if it is Size 10, Italic and Times New Roman.
        ///          * SubsetMatch means that the formatting must contain all elements of the match formatting,
        ///          * but it can also contain additional formatting for example Color, UnderlineStyle, etc.
        ///          * ExactMatch means it must not contain additional formatting.
        ///          */
        ///         p.ReplaceText("wrong", "right", false, RegexOptions.IgnoreCase, newFormatting, matchFormatting, MatchFormattingOptions.SubsetMatch);
        ///     }
        ///
        ///     // Save all changes made to this document.
        ///     document.Save();
        /// }// Release this document from memory.
        /// </code>
        /// </example>
        /// <seealso cref="Paragraph.RemoveText(int, int, bool)"/>
        /// <seealso cref="Paragraph.RemoveText(int, bool)"/>
        /// <seealso cref="Paragraph.InsertText(string, bool)"/>
        /// <seealso cref="Paragraph.InsertText(int, string, bool)"/>
        /// <seealso cref="Paragraph.InsertText(int, string, bool, Formatting)"/>
        /// <seealso cref="Paragraph.InsertText(string, bool, Formatting)"/>
        /// <param name="newValue">A System.String to replace all occurances of oldValue.</param>
        /// <param name="oldValue">A System.String to be replaced.</param>
        /// <param name="options">A bitwise OR combination of RegexOption enumeration options.</param>
        /// <param name="trackChanges">Track changes</param>
        /// <param name="newFormatting">The formatting to apply to the text being inserted.</param>
        /// <param name="matchFormatting">The formatting that the text must match in order to be replaced.</param>
        /// <param name="fo">How should formatting be matched?</param>
        public void ReplaceFirstText(string oldValue, string newValue, bool trackChanges = false, RegexOptions options = RegexOptions.None, Formatting newFormatting = null, Formatting matchFormatting = null, MatchFormattingOptions fo = MatchFormattingOptions.SubsetMatch)
        {
            MatchCollection mc = Regex.Matches(this.Text, Regex.Escape(oldValue), options);

            // Loop through the matches in reverse order
            foreach (Match m in mc.Cast<Match>())
            {
                // Assume the formatting matches until proven otherwise.
                bool formattingMatch = true;

                // Does the user want to match formatting?
                if (matchFormatting != null)
                {
                    // The number of characters processed so far
                    int processed = 0;

                    do
                    {
                        // Get the next run effected
                        Run run = GetFirstRunEffectedByEdit(m.Index + processed);

                        // Get this runs properties
                        XElement rPr = run.Xml.Element(XName.Get("rPr", DocX.w.NamespaceName));

                        if (rPr == null)
                            rPr = new Formatting().Xml;

                        /* 
                         * Make sure that every formatting element in f.xml is also in this run,
                         * if this is not true, then their formatting does not match.
                         */
                        if (!ContainsEveryChildOf(matchFormatting.Xml, rPr, fo))
                        {
                            formattingMatch = false;
                            break;
                        }

                        // We have processed some characters, so update the counter.
                        processed += run.Value.Length;

                    } while (processed < m.Length);
                }

                // If the formatting matches, do the replace.
                if (formattingMatch)
                {
                    InsertText(m.Index + oldValue.Length, newValue, trackChanges, newFormatting);
                    RemoveText(m.Index, m.Length, trackChanges);
                    return;
                }
            }
        }

        public void InsertTextBeforeFirstVisibleChar(string text)
        {
            var parcontent = this.Text;
            if (parcontent == "")
            {
                this.Append(text);
            }
            else
            {
                var idx = 0;
                var length = parcontent.Length;
                while (true)
                {
                    if (idx > length - 1)
                        break;
                    var ch = parcontent[idx];
                    if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
                        idx++;
                    else
                        break;
                }
                if (idx > length - 1)
                    idx = length - 1;
                var leadingInvisibleText = parcontent.Substring(idx, 1);
                this.ReplaceFirstText(leadingInvisibleText, text + leadingInvisibleText);
            }
        }
    }

}
